/*
 * =====================================================================================
 *    Description:  unit tests for simple iris interface
 *
 *        Version:  1.0
 *        Created:  08/29/2011 
 *
 *         Author:  Zhenjiang Dong
 *         School:  Georgia Institute of Technology
 *
 * =====================================================================================
 */
#include <TestFixture.h>
#include <TestAssert.h>
#include <TestSuite.h>
#include <Test.h>
#include <TestCaller.h>
#include <cppunit/ui/text/TestRunner.h>

#include <iostream>
#include <list>
#include <stdlib.h>
#include "../../data_types/linkData.h"
#include "../../data_types/flit.h"
#include "../../components/genericBuffer.h"
#include "../../interfaces/genericHeader.h"
#include "genOneVcIrisInterface.h"

#include "kernel/manifold.h"
#include "kernel/component.h"

using namespace manifold::kernel;
using namespace std;

using namespace manifold::iris;

//####################################################################
// helper classes
//####################################################################

class TerminalData {
public:
    uint src; //must have this 
    uint dest_id;
    uint get_src() { return src; } //must have
    uint get_dst() { return dest_id; }
};

//! This class emulates a terminal
class MockSink : public manifold::kernel::Component {
public:
    enum {IN=0};

    void handle_incoming_pkt (int, TerminalData* pkt)
    {
        m_pkts.push_back(*pkt);
    }
    
    void handle_incoming_lnkdt (int, LinkData* ld)
    {
        m_lnkdt.push_back(*ld);
    }

    list<TerminalData>* get_pkts() { return &m_pkts; }
    list<LinkData>* get_lnkdt() { return &m_lnkdt; }
private:
    list<TerminalData> m_pkts;
    list<LinkData> m_lnkdt;
};

class MockLinkDataGenerator : public manifold::kernel::Component {
public:
    enum {IN=0};
    uint flit_count;
    uint linkwidth;
    
    MockLinkDataGenerator(simple_inf_init_params* i_p)
    {
        flit_count = 0;
        linkwidth = i_p->linkWidth;
    }

    void send_lnkdt_flit (int vc)
    {
        //calculate the total length
        uint no_bf = uint ((sizeof(TerminalData)*8)/linkwidth);
        double compare =double (sizeof(TerminalData)*8)/ linkwidth;
    
        if (compare > no_bf)
           no_bf ++;
           
        uint total = no_bf + 2;
        
        //puch a entire pkt at each router in buffer
        if (flit_count%total == 0)
        {
           HeadFlit* f = new HeadFlit();
           f->type = HEAD;
           f->pkt_length = total;
           f->src_id = 0;
           f->dst_id = 0;
           
           LinkData* ld = new LinkData;
           ld->type = FLIT;
           ld->src = this->GetComponentId();
           ld->f = f;
           ld->vc = vc;
           Send(3 , ld);
           flit_count++;
        }
        else if (flit_count%total == (total - 1))
        {
           TailFlit* f = new TailFlit();
           f->type = TAIL;
           f->pkt_length = total;
           f->enter_network_time = Manifold::NowTicks();
           
           LinkData* ld = new LinkData;
           ld->type = FLIT;
           ld->src = this->GetComponentId();
           ld->f = f;
           ld->vc = vc;
           Send(3 , ld);
           flit_count++;
        }
        else
        {
           BodyFlit* f = new BodyFlit();
           f->type = BODY;
           f->pkt_length = total;
           
           LinkData* ld = new LinkData;
           ld->type = FLIT;
           ld->src = this->GetComponentId();
           ld->f = f;
           ld->vc = vc;
           Send(3 , ld);
           flit_count++;
        } 
    }
    
    void send_lnkdt_credit (int vc)
    {
        LinkData* ld = new LinkData;
        ld->type = CREDIT;
        ld->vc = vc;
        Send(3 , ld);
    }
};



//####################################################################
//! Class NetworkInterfaceTest is the test class for class NetworkInterface. 
//####################################################################
class GenOneVcIrisInterfaceTest : public CppUnit::TestFixture {
private:

public:
    //! Initialization function. Inherited from the CPPUnit framework.
    static Clock MasterClock;  //clock has to be global or static.
    enum { MASTER_CLOCK_HZ = 10 };
    
    //======================================================================
    //======================================================================
    //! @brief Test init() of simple interface: vector size and initial value.
    //!
    //! create an interface and call init() function with given parameters
    //! Verify the vectors' size and the initial values.    
    void test_Interface_init_0()
    {
        //initialzing the interface and the arbiter will be init also
        simple_inf_init_params* i_p = new simple_inf_init_params;
        i_p->linkWidth = 128;
        i_p->num_credits = 3;
    
	Simple_terminal_to_net_mapping* mapping = new Simple_terminal_to_net_mapping();
        GenOneVcIrisInterface<TerminalData>* GnI = new GenOneVcIrisInterface<TerminalData>(0, mapping, i_p);
        
        //test whether the inital value are correct
        CPPUNIT_ASSERT_EQUAL(bool(GnI->is_proc_out_buffer_free), true);
        CPPUNIT_ASSERT_EQUAL(uint(GnI->downstream_credits), GnI->credits); 
    }

    //======================================================================
    //======================================================================
    //! @brief Test handle_new_packet_event() of simple interface
    //!
    //! Verify whether the incoming pkt has been received and saved correctly
    void test_Interface_handle_new_packet_event_0 ()
    {
        //initialzing the interface and the arbiter will be init also
        simple_inf_init_params* i_p = new simple_inf_init_params;
        i_p->linkWidth = 128;
        i_p->num_credits = 3;
    
	Simple_terminal_to_net_mapping* mapping = new Simple_terminal_to_net_mapping();
        GenOneVcIrisInterface<TerminalData>* GnI = new GenOneVcIrisInterface<TerminalData>(0, mapping, i_p);  
        
        //test      
        CPPUNIT_ASSERT_EQUAL(0,int(GnI->input_pkt_buffer.size()));
        TerminalData* td = new TerminalData;
        td->src = random()%10;
        td->dest_id = random()%10;
        GnI->handle_new_packet_event(GenOneVcIrisInterface<TerminalData>::DATAFROMTERMINAL, td);
        TerminalData* td_1 = GnI->input_pkt_buffer.front();
        CPPUNIT_ASSERT_EQUAL(td->src, td_1->src);
        CPPUNIT_ASSERT_EQUAL(td->dest_id, td_1->dest_id);
    }
    
    //======================================================================
    //======================================================================
    //! @brief Test to_flit_level_packet() of simple interface
    //!
    //! Verify whether the pkt has been successfully transfered to flits level pkt
    void test_Interface_to_flit_level_packet_0()
    {
        //initialzing the interface and the arbiter will be init also
        simple_inf_init_params* i_p = new simple_inf_init_params;
        i_p->linkWidth = 128;
        i_p->num_credits = 3;
    
	Simple_terminal_to_net_mapping* mapping = new Simple_terminal_to_net_mapping();
	const unsigned IF_ID = random() % 100; //interface ID
        GenOneVcIrisInterface<TerminalData>* GnI = new GenOneVcIrisInterface<TerminalData>(IF_ID, mapping, i_p);
        
        //calculate the total length
        uint no_bf = uint ((sizeof(TerminalData)*8)/i_p->linkWidth);
        double compare =double (sizeof(TerminalData)*8)/ i_p->linkWidth;
    
        if (compare > no_bf)
           no_bf ++;
           
        uint total = no_bf + 2;        
        
        //test
        TerminalData* td = new TerminalData;
        td->src = random()%10;
        td->dest_id = random()%10;
        FlitLevelPacket* flp = new FlitLevelPacket;
        GnI->to_flit_level_packet(flp, i_p->linkWidth, td);
        //see if the dest and src id passed correctly
        CPPUNIT_ASSERT_EQUAL(total, flp->size());
        CPPUNIT_ASSERT_EQUAL(td->dest_id, (uint)flp->dst_id);
        CPPUNIT_ASSERT_EQUAL(IF_ID, flp->src_id); //FlipLevelPacket's src_node must be the interface id.
        
        //see if the flits be generated correctly
        for (unsigned i = 0; i < flp->pkt_length; i++)
        {
            if (i == 0)
               CPPUNIT_ASSERT_EQUAL(flp->flits[i]->type, HEAD);
               
            else if (i == (flp->pkt_length - 1))
               CPPUNIT_ASSERT_EQUAL(flp->flits[i]->type, TAIL);
               
            else
               CPPUNIT_ASSERT_EQUAL(flp->flits[i]->type, BODY);
        }
    }

    //======================================================================
    //======================================================================
    //! @brief Test tock() of simple interface
    //!
    //! Verify whether the flits assign to outgoing buffers correctly with right dest & src address     
    void test_Interface_tock_0()
    {
        //initialzing the interface and the arbiter will be init also
        simple_inf_init_params* i_p = new simple_inf_init_params;
        i_p->linkWidth = 128;
        i_p->num_credits = 3;
    
	Simple_terminal_to_net_mapping* mapping = new Simple_terminal_to_net_mapping();
	const unsigned IF_ID = random() % 100; //interface ID
        GenOneVcIrisInterface<TerminalData>* GnI = new GenOneVcIrisInterface<TerminalData>(IF_ID, mapping, i_p);
        
        //calculate the total length
        uint no_bf = uint ((sizeof(TerminalData)*8)/i_p->linkWidth);
        double compare =double (sizeof(TerminalData)*8)/ i_p->linkWidth;
    
        if (compare > no_bf)
           no_bf ++;
           
        uint total = no_bf + 2;         
        
        //test
        for (unsigned i = 0; i < (4 + 1); i++)
        {
            TerminalData* td = new TerminalData;
            td->src = i;
            td->dest_id = i;
            GnI->handle_new_packet_event(GenOneVcIrisInterface<TerminalData>::DATAFROMTERMINAL,td);
        }
        
        //see if the handle_new_packet_event put all pkt into the buffer
        CPPUNIT_ASSERT_EQUAL(uint(GnI->input_pkt_buffer.size()), uint(4 + 1));
        
        //see if the buffer allocate process is correct
        GnI->tock();
        CPPUNIT_ASSERT_EQUAL(int(GnI->input_pkt_buffer.size()), 4);
        
        CPPUNIT_ASSERT_EQUAL(bool(GnI->is_proc_out_buffer_free), false);
        CPPUNIT_ASSERT_EQUAL(uint(total), GnI->proc_out_buffer.size());
        CPPUNIT_ASSERT_EQUAL(0, (int)GnI->proc_out_buffer.dst_id);
        CPPUNIT_ASSERT_EQUAL(IF_ID, GnI->proc_out_buffer.src_id);
    }


    //======================================================================
    //======================================================================
    //! @brief Test tick() of simple interface
    //!
    //! Verify flit sending process and the behavior of outgoing buffer
    void test_Interface_tick_0()
    {
        //initialzing the interface
        simple_inf_init_params* i_p = new simple_inf_init_params;
        i_p->linkWidth = 128;
        i_p->num_credits = 3;
        
	Simple_terminal_to_net_mapping* mapping = new Simple_terminal_to_net_mapping();
        CompId_t ni_cid = Component :: Create<GenOneVcIrisInterface<TerminalData> > (0, 0, mapping, i_p);
        CompId_t ms_cid = Component :: Create<MockSink> (0);
        
        GenOneVcIrisInterface<TerminalData>* GnI = Component :: GetComponent<GenOneVcIrisInterface<TerminalData> >(ni_cid);
        MockSink* MTer = Component :: GetComponent<MockSink>(ms_cid);
        
        
        Manifold::Connect(ni_cid, GenOneVcIrisInterface<TerminalData>::DATAOUT, ms_cid, 
                          0, &MockSink::handle_incoming_lnkdt,1);
        Manifold::Connect(ni_cid, GenOneVcIrisInterface<TerminalData>::DATATOTERMINAL, ms_cid, 
                          2, &MockSink::handle_incoming_pkt,1);
                          
        //calculate the total length
        uint no_bf = uint ((sizeof(TerminalData)*8)/i_p->linkWidth);
        double compare =double (sizeof(TerminalData)*8)/ i_p->linkWidth;
    
        if (compare > no_bf)
           no_bf ++;
           
        uint total = no_bf + 2;         
        
        //test
        for (unsigned i = 0; i < (4 + 1); i++)
        {
            TerminalData* td = new TerminalData;
            td->src = i;
            td->dest_id = i;
            GnI->handle_new_packet_event(GenOneVcIrisInterface<TerminalData>::DATAFROMTERMINAL,td);
        }
        
        GnI->tock();
        CPPUNIT_ASSERT_EQUAL(uint(total), GnI->proc_out_buffer.size());
        
        //test the function of transfering flits in proc buffer to router buffer
        for (int i = 0; i < 100; i++)
            GnI->tick();
        
        CPPUNIT_ASSERT_EQUAL(uint(0), GnI->proc_out_buffer.size());
        
        Manifold::unhalt();
        Manifold::StopAt(10);
        Manifold::Run();
        
        list<LinkData>* ld = MTer->get_lnkdt();
        CPPUNIT_ASSERT_EQUAL((i_p->num_credits), uint(ld->size()));
    }

    //======================================================================
    //======================================================================
    //! @brief Test tick() of simple interface
    //!
    //! Verify pkt sending process and the behavior of incoming buffer
    void test_Interface_tick_1()
    {
        //initialzing the interface
        simple_inf_init_params* i_p = new simple_inf_init_params;
        i_p->linkWidth = 128;
        i_p->num_credits = 3;
        
	Simple_terminal_to_net_mapping* mapping = new Simple_terminal_to_net_mapping();
        CompId_t ni_cid = Component :: Create<GenOneVcIrisInterface<TerminalData> > (0, 0, mapping, i_p);
        GenOneVcIrisInterface<TerminalData>* GnI = Component :: GetComponent<GenOneVcIrisInterface<TerminalData> >(ni_cid);
        CompId_t ms_cid = Component :: Create<MockSink> (0);
        MockSink* MTer = Component :: GetComponent<MockSink>(ms_cid);
        
        Manifold::Connect(ni_cid, GenOneVcIrisInterface<TerminalData>::DATAOUT, ms_cid, 
                          0, &MockSink::handle_incoming_lnkdt,1);
        Manifold::Connect(ni_cid, GenOneVcIrisInterface<TerminalData>::DATATOTERMINAL, ms_cid, 
                          2, &MockSink::handle_incoming_pkt,1);        
        
        
        //calculate the total length
        uint no_bf = uint ((sizeof(TerminalData)*8)/i_p->linkWidth);
        double compare =double (sizeof(TerminalData)*8)/ i_p->linkWidth;
    
        if (compare > no_bf)
           no_bf ++;
           
        uint total = no_bf + 2;
        
        //puch a entire pkt at each router in buffer
        for (unsigned j = 0; j < total; j++)
        {
            if (j == 0)
            {
               HeadFlit* f = new HeadFlit();
               f->type = HEAD;
               f->pkt_length = total;
               f->src_id = 0;
               f->dst_id = 0;
               GnI->proc_in_buffer.add(f);
            }
            else if (j == (total - 1))
            {
               TailFlit* f = new TailFlit();
               f->type = TAIL;
               f->pkt_length = total;
               f->enter_network_time = Manifold::NowTicks();
               GnI->proc_in_buffer.add(f);
            }
            else
            {
               BodyFlit* f = new BodyFlit();
               f->type = BODY;
               f->pkt_length = total;
               GnI->proc_in_buffer.add(f);
            }   
        }
        
        for (int i = 0; i < 100; i++)
            GnI->tick();
        
        Manifold::unhalt();
        Manifold::StopAt(10);
        Manifold::Run();       
        
        CPPUNIT_ASSERT_EQUAL(0, int(GnI->proc_in_buffer.size()));
        
        list<TerminalData>* td = MTer->get_pkts();     
        CPPUNIT_ASSERT_EQUAL(uint(1), uint(td->size())); 
        
        //list<LinkData>* ld = MTer->get_lnkdt();     
        //CPPUNIT_ASSERT_EQUAL(1, uint(ld->size()));       
    }

    //======================================================================
    //======================================================================
    //! @brief Test handle_router() of simple interface
    //!
    //! Verify the process to handle incoming LinkData    
    void test_Interface_handle_router_0()
    {
        //initialzing the interface
        simple_inf_init_params* i_p = new simple_inf_init_params;
        i_p->linkWidth = 128;
        i_p->num_credits = 3;

	Simple_terminal_to_net_mapping* mapping = new Simple_terminal_to_net_mapping();
        CompId_t ni_cid = Component :: Create<GenOneVcIrisInterface<TerminalData> > (0, 0, mapping, i_p);
        CompId_t lg_cid = Component :: Create<MockLinkDataGenerator> (0, i_p);
        CompId_t ms_cid = Component :: Create<MockSink> (0);
        
        GenOneVcIrisInterface<TerminalData>* GnI = Component :: GetComponent<GenOneVcIrisInterface<TerminalData> >(ni_cid);
        MockLinkDataGenerator* Mlg = Component :: GetComponent<MockLinkDataGenerator>(lg_cid);

	Manifold::Connect(ni_cid, GenOneVcIrisInterface<TerminalData>::DATAOUT, ms_cid, 
                          1, &MockSink::handle_incoming_lnkdt,1);
        Manifold::Connect(lg_cid, 3, ni_cid, GenOneVcIrisInterface<TerminalData>::DATAIN,
                          &GenOneVcIrisInterface<TerminalData>::handle_router,1);
                          
	GnI->downstream_credits = 0; //manually set credits to 0

        for (unsigned j = 0; j < i_p->num_credits; j++)
        {
            Mlg->send_lnkdt_flit (0);
            Mlg->send_lnkdt_credit (0);
        }
        
        Manifold::unhalt();
        Manifold::StopAt(10);
        Manifold::Run();  
        
        CPPUNIT_ASSERT_EQUAL(i_p->num_credits, uint(GnI->proc_in_buffer.size())); 
        CPPUNIT_ASSERT_EQUAL(i_p->num_credits, uint(GnI->downstream_credits));
    }
    
    //! Build a test suite.
    static CppUnit::Test* suite()
    {
	CppUnit::TestSuite* mySuite = new CppUnit::TestSuite("NnewIrisInterfaceTest");

	mySuite->addTest(new CppUnit::TestCaller<GenOneVcIrisInterfaceTest>("test_Interface_init_0", 
	                 &GenOneVcIrisInterfaceTest::test_Interface_init_0));
	mySuite->addTest(new CppUnit::TestCaller<GenOneVcIrisInterfaceTest>("test_Interface_handle_new_packet_event_0", 	                       &GenOneVcIrisInterfaceTest::test_Interface_handle_new_packet_event_0));
	mySuite->addTest(new CppUnit::TestCaller<GenOneVcIrisInterfaceTest>("test_Interface_to_flit_level_packet_0", 	                    				 &GenOneVcIrisInterfaceTest::test_Interface_to_flit_level_packet_0));
	mySuite->addTest(new CppUnit::TestCaller<GenOneVcIrisInterfaceTest>("test_Interface_tick_0",
	                 &GenOneVcIrisInterfaceTest::test_Interface_tick_0));
	mySuite->addTest(new CppUnit::TestCaller<GenOneVcIrisInterfaceTest>("test_Interface_tick_1",
	                 &GenOneVcIrisInterfaceTest::test_Interface_tick_1));  
        mySuite->addTest(new CppUnit::TestCaller<GenOneVcIrisInterfaceTest>("test_Interface_tock_0",
	                 &GenOneVcIrisInterfaceTest::test_Interface_tock_0));
        mySuite->addTest(new CppUnit::TestCaller<GenOneVcIrisInterfaceTest>("test_Interface_handle_router_0",
	                 &GenOneVcIrisInterfaceTest::test_Interface_handle_router_0));
	return mySuite;
    }
};

Clock GenOneVcIrisInterfaceTest :: MasterClock(GenOneVcIrisInterfaceTest :: MASTER_CLOCK_HZ);

int main()
{
    Manifold :: Init();
    CppUnit::TextUi::TestRunner runner;
    runner.addTest( GenOneVcIrisInterfaceTest::suite() );
    if(runner.run("", false))
	return 0; //all is well
    else
	return 1;

}


